﻿using System;
using System.Collections.Generic;
using System.Text;
using SharpCommunications.Interfaces;
using System.Threading;
using System.Net.Sockets;
using SharpCommunications.Logging;
using System.IO;
using SharpCommunications.Cryptography;

namespace SharpCommunications.Connection
{
    public class SharpConnectionEndpoint : ISharpConnectionEndpoint, IDisposable
    {
        protected bool generateReceiveEvents;
        protected Thread receivingThread;
        protected bool isClosed;
        protected TcpClient tcpClient;
        protected ISharpCryptographer cryptographer;

        public SharpConnectionEndpoint(ISharpCryptographer cryptographer = null, bool generateReceiveEvents = true)
        {
            this.cryptographer = cryptographer;
            this.generateReceiveEvents = generateReceiveEvents;
            this.receivingThread = null;
            this.isClosed = false;
            this.tcpClient = null;
        }

        public void Initialize(TcpClient tcpClient)
        {
            this.tcpClient = tcpClient;

            if (this.generateReceiveEvents)
            {
                this.receivingThread = new Thread(new ThreadStart(this.ReceivingThread));
                this.receivingThread.IsBackground = true;
                this.receivingThread.Name = "ConnectionEndpoint.ReceivingThread";
                this.receivingThread.Start();
            }
        }

        protected virtual void ReceivingThread()
        {
            Location location = new Location("SharpCommunications.Connection.ConnectionEndpoint.Receivingthread()");

            while (this.tcpClient.Connected)
            {
                try
                {
                    ReadMessage();
                }
                catch (Exception) //exception is processed in ReceiveMessage()
                {
                    return;
                }
            }

            NotifyDisconnected();
        }

        protected void NotifyDisconnected()
        {
            if(this.Disconnected != null)
            {
                Disconnected(this);
            }
        }

        protected string ReadMessage()
        {
            Location location = new Location("SharpCommunications.Connection.ConnectionEndpoint.ReadMessage()");

            try
            {
                NetworkStream stream = this.tcpClient.GetStream();

                BinaryReader binaryReader = new BinaryReader(stream);
                string message = binaryReader.ReadString();

                if(this.cryptographer != null)
                {
                    message = this.cryptographer.DecodeFromBase64AndDecrypt(message);
                }

                if (this.generateReceiveEvents && ReceivedMessage != null)
                {
                    try
                    {
                        ReceivedMessage(this, message);
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                }

                return message;
            }
            catch (EndOfStreamException eosex)
            {
                NotifyDisconnected();
                throw;
            }
            catch (IOException ioex)
            {
                if (ioex.InnerException != null && ioex.InnerException.Message.StartsWith("A blocking operation was interrupted"))
                {
                    NotifyDisconnected();
                    throw;
                }
                else
                {
                    LogFactory.Log.ErrorMessage(location, ioex, true);
                    NotifyDisconnected();
                    throw;
                }
            }
            catch (Exception ex)
            {
                LogFactory.Log.ErrorMessage(location, ex, true);
                NotifyDisconnected();
                throw;
            }

        }

        #region IConnectionEndpoint Members

        public event ReceivedMessageHandlerDelegate ReceivedMessage;
        public event DisconnectedHandlerDelegate Disconnected;

        public string ReceiveMessage()
        {
            if(this.generateReceiveEvents)
            {
                throw new ArgumentException("ConnectionEndpoint is in the mode to generate ReceivedMessage events - subscribe to the event instead of calling ReceiveMessage()");
            }

            return ReadMessage();
        }

        public void SendMessage(string message)
        {
            Location location = new Location("SharpCommunications.Connection.ConnectionEndpoint.SendMessage()");

            try
            {
                if(this.cryptographer != null)
                {
                    message = this.cryptographer.EncryptAndEncodeToBase64(message);
                }

                NetworkStream stream = this.tcpClient.GetStream();

                BinaryWriter bw = new BinaryWriter(stream);
                bw.Write(message);
            }
            catch (EndOfStreamException eosex)
            {
                NotifyDisconnected();
                throw;
            }
            catch (IOException ioex)
            {
                if (ioex.InnerException != null && ioex.InnerException.Message.StartsWith("A blocking operation was interrupted"))
                {
                    NotifyDisconnected();
                    throw;
                }
                else
                {
                    LogFactory.Log.ErrorMessage(location, ioex, true);
                    NotifyDisconnected();
                    throw;
                }
            }
            catch (Exception ex)
            {
                LogFactory.Log.ErrorMessage(location, ex, true);
                NotifyDisconnected();
                throw;
            }

        }

        public void Close()
        {
            if (!this.isClosed)
            {
                this.isClosed = true;

                if (this.tcpClient.Connected)
                {
                    NetworkStream networkStream = this.tcpClient.GetStream();
                    networkStream.Close();
                }
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Close();            
        }


        #endregion
    }
}
